function Compile(el, vm) {
  // el 就是配置对象中的el属性的值
  // vm 就是vm实例
  // 将vm实例存储到Compile实例的$vm属性上,一定是为了后面代码使用
  this.$vm = vm
  // 判断el传入的是dom还是字符串. 如果是dom则直接将dom赋值给$el,如果不是dom则根据选择器获取对应的dom
  this.$el = this.isElementNode(el) ? el : document.querySelector(el)
  // 判断是否获取到了dom
  if (this.$el) {
    // 如果成功的拿到了dom,才执行后续的代码

    // 将模板中的所有子节点添加到文档碎片中
    // this.$fragment中存储了模板中的所有节点
    this.$fragment = this.node2Fragment(this.$el)
    // 对模板中所有的节点进行解析(周一见)
    this.init()
    // 将解析好的文档碎片中的dom重新插入到模板中,那么页面就呈现出解析之后的内容了
    this.$el.appendChild(this.$fragment)
  }
}

Compile.prototype = {
  // 将dom树中节点添加到文档碎片的方法
  node2Fragment: function (el) {
    // 创建文档碎片
    var fragment = document.createDocumentFragment(),
      child
    // 将模板中所有的子节点,一个一个的添加到文档碎片中,什么时候添加完毕了,就结束while循环
    while ((child = el.firstChild)) {
      fragment.appendChild(child)
    }
    // 返回文档碎片
    return fragment
  },

  init: function () {
    // 解析节点的方法
    this.compileElement(this.$fragment)
  },

  compileElement: function (el) {
    // 获取当前节点下面的所有子节点
    var childNodes = el.childNodes,
      me = this
    // 将存储子节点的伪数组转成数组,然后遍历
    ;[].slice.call(childNodes).forEach(function (node) {
      // 获取节点的内容
      var text = node.textContent
      // 定义正在,用来解析插值表达式
      var reg = /\{\{(.*)\}\}/
      // 判断当前节点是否是元素节点
      if (me.isElementNode(node)) {
        // 如果是,则调用complie方法,解析元素上的属性(指令)
        me.compile(node)
        // 判断当前节点是否是文本节点,如果是还要判断这个文本是否写了插值表达式
      } else if (me.isTextNode(node) && reg.test(text)) {
        // 如果是插值表达式,则解析插值表达式,将插值表达式中所对应的数据解析出来
        me.compileText(node, RegExp.$1)
      }
      // 判断当前节点下面是否还有子节点,如果有则递归进入遍历子节点
      if (node.childNodes && node.childNodes.length) {
        me.compileElement(node)
      }
    })
  },

  compile: function (node) {
    // 获取节点上面的所有属性
    var nodeAttrs = node.attributes,
      me = this
    // 将伪数组转成真数组进行比那里
    ;[].slice.call(nodeAttrs).forEach(function (attr) {
      // 注意: node.attributes可以获取到所有的属性,并存储在数组中.但是这些属性是以对象的形式存储到数组中的.所以遍历时attr是一个个的对象
      var attrName = attr.name // 拿到了属性名,这个才是字符串  v-on:click
      // 通过属性名进行判断,是否是指令(所有的指令都是v-on开头)
      if (me.isDirective(attrName)) {
        // 获取属性所对应的值
        var exp = attr.value
        // 去掉指令的v-
        var dir = attrName.substring(2)
        // 是否是事件指令(判断依据: 是否以on开头)
        if (me.isEventDirective(dir)) {
          // 调用方法,给节点绑定事件
          compileUtil.eventHandler(node, me.$vm, exp, dir)
        } else {
          // 根据指令找到对应的方法,比如执行是v-text.则根据text找到compileUtil.text方法
          compileUtil[dir] && compileUtil[dir](node, me.$vm, exp)
        }
        node.removeAttribute(attrName)
      }
    })
  },

  compileText: function (node, exp) {
    //继续调用text方法
    compileUtil.text(node, this.$vm, exp)
  },

  isDirective: function (attr) {
    return attr.indexOf('v-') == 0
  },

  isEventDirective: function (dir) {
    return dir.indexOf('on') === 0
  },

  isElementNode: function (node) {
    return node.nodeType == 1
  },

  isTextNode: function (node) {
    return node.nodeType == 3
  },
}

var compileUtil = {
  text: function (node, vm, exp) {
    // 继续调用bind方法
    this.bind(node, vm, exp, 'text')
  },

  html: function (node, vm, exp) {
    this.bind(node, vm, exp, 'html')
  },

  model: function (node, vm, exp) {
    this.bind(node, vm, exp, 'model')

    var me = this,
      val = this._getVMVal(vm, exp)
    node.addEventListener('input', function (e) {
      debugger
      var newValue = e.target.value
      if (val === newValue) {
        return
      }

      me._setVMVal(vm, exp, newValue)
      val = newValue
    })
  },

  class: function (node, vm, exp) {
    this.bind(node, vm, exp, 'class')
  },

  bind: function (node, vm, exp, dir) {
    // 获取到解析当前节点合适的方法
    // v-text/ {{}} --> dir -->text
    // v-html --> dir -->html
    // 然后通过updater找到对应函数,比如以text为例,中找到了updater中的textUpdater
    var updaterFn = updater[dir + 'Updater']
    // 然后调用这个合适的方法

    updaterFn && updaterFn(node, this._getVMVal(vm, exp))
    // 1. 只要模板中绑定数据没会new 一个Watcher
    new Watcher(vm, exp, function (value, oldValue) {
      updaterFn && updaterFn(node, value, oldValue)
    })
  },

  eventHandler: function (node, vm, exp, dir) {
    // dir就是属性名 ==> on:click
    // eventType ==> click
    var eventType = dir.split(':')[1],
      // 判断vue实例上是否有methods这个配置项,如果有则根据事件名,拿到事件处理函数
      fn = vm.$options.methods && vm.$options.methods[exp]
    // 如果事件名也拿到了,事件处理函数也拿到了,则直接利用dom的方法给节点绑定事件
    if (eventType && fn) {
      // fn.bind(vm) 根据已有的事件处理函数，克隆出来一个新的函数。新的函数才是真正的事件处理函数，并且新函数的this固定指向了vue实例
      node.addEventListener(eventType, fn.bind(vm), false)
    }
  },

  _getVMVal: function (vm, exp) {
    var val = vm._data
    exp = exp.split('.')
    exp.forEach(function (k) {
      val = val[k]
    })
    return val
  },

  _setVMVal: function (vm, exp, value) {
    var val = vm._data
    exp = exp.split('.')
    exp.forEach(function (k, i) {
      if (i < exp.length - 1) {
        val = val[k]
      } else {
        val[k] = value
      }
    })
  },
}

var updater = {
  textUpdater: function (node, value) {
    // 就是将解析出来的数据,以文本的形式添加到节点中
    node.textContent = typeof value == 'undefined' ? '' : value
  },

  htmlUpdater: function (node, value) {
    node.innerHTML = typeof value == 'undefined' ? '' : value
  },

  classUpdater: function (node, value, oldValue) {
    var className = node.className
    className = className.replace(oldValue, '').replace(/\s$/, '')

    var space = className && String(value) ? ' ' : ''

    node.className = className + space + value
  },

  modelUpdater: function (node, value, oldValue) {
    node.value = typeof value == 'undefined' ? '' : value
  },
}
